#include "stdafx.h"
#include <AES128.h>
  
AES128::AES128() {

	Nr = 10;
	Nb = 4;
	Nk = 4;  
	debug = false;
	toHex = false;

    key = (int ***)malloc(11 * sizeof(int ***));

    for(int i=0; i < 11; i++) {
        key[i]=(int **)malloc(11 * sizeof(int **));
        for(int j=0; j < 11; j++) 
           key[i][j]=(int *)malloc(11 *sizeof(int *));
           
    } 

	state = (int **)malloc(4 *sizeof(int **));
    for(int i=0; i < 4; i++)
        state[i]=(int *)malloc(4*sizeof(int *));
        
};

AES128::~AES128() {

	debug= false;
	toHex= false;
    for(int i=0; i < 11; i++) 
        for(int j=0; j < 11; j++) 
            free(key[i][j]); 
	for (int i=0; i < 4; i++)
		free(state[i]);
       
};
    /**

     * Realiza una XOR entre S-box y la clave i-esima rk.

     *

     * @param rk  Array de caracteres (4x4) con la clave i-esima

     */


void AES128::KeyAddition(int **rk) {
    
	for (int i = 0; i < 4; i++)
		for (int j = 0; j < Nb; j++)
		   state[i][j] ^= rk[i][j]; 

}

	    
void AES128::ByteSubShiftRow(){

		int tmp[4][4];
		for(int i = 0; i < 4; i++)
		   for(int j = 0; j < Nb; j++)                
			  tmp[i][shifts_r[i][j]]= S[state[i][j]];
	
		for(int i=0; i<4; i++)
			memcpy(state[i], tmp[i], 4 * sizeof(int)); 
	}


void AES128::MixColumnKeyAddition(int **rk){

		 int b[4][4];
		 for(int j = 0; j < 4; j++)
			for(int i = 0; i < Nb; i++){
			   b[i][j] = T2[state[i][j]] ^ T3[state[(i+1) % 4][j]] ^ state[(i + 2) % 4][j] ^ state[(i + 3) % 4][j];
			   b[i][j]^=rk[i][j];

			}

		 for(int i=0; i<4; i++)
			memcpy(state[i], b[i], 4 * sizeof(int)); 

	}

void AES128::InvMixColumn() {

		 int b[4][4];
		 for(int j = 0; j < 4; j++)
			for(int i = 0; i < Nb; i++)
			   b[i][j] = T14[state[i][j]] ^ T11[state[(i + 1) % 4][j]] ^ T13[state[(i + 2) % 4][j]] ^ T9[state[(i + 3) % 4][j]];

		 for(int i=0; i<4; i++)
			memcpy(state[i], b[i], 4 * sizeof(int)); 
	}


void AES128::InvShiftRowInvByteSub() {        
	      
		int tmp[4][4];
		for(int i = 0; i < 4; i++)
		   for(int j = 0; j < Nb; j++)
			  tmp[i][shifts_l[i][j]]= Si[state[i][j]];

		for(int i=0; i<4; i++)
			memcpy(state[i], tmp[i], 4 * sizeof(int)); 
	}  



    
void AES128::makeKey(char *hash) {

		int rconpocharer = 0;
		int tk[4][4];
	
		for(int j = 0; j < Nk; j++)
			for(int i = 0; i < 4; i++) {
			   tk[i][j] = (int)(hash[j*4+i])>256 ? (int)(hash[j*4+i])%256 : (int)(hash[j*4+i]);
			   //printf(" %i \n",tk[i][j]); 
			}

		int t = 0;

		for(int j = 0; (j < Nk) && (t < (Nr+1)*Nb); j++, t++)
			for(int i = 0; i < 4; i++) {
				key[t / Nb][i][t % Nb] = tk[i][j]; 
                // printf("%i %i %i %i %i\n", t/Nb, i, t%Nb, key[t / Nb][i][t % Nb], tk[i][j]);
			}

		//printf("t=%i \n", t);

	   while (t < (Nr+1)*Nb) {          

			    for (int i = 0; i < 4; i++) {
					//printf("p= %i %i\n",tk[i][0], S[tk[(i+1)%4][Nk-1]]);
					tk[i][0] = tk[i][0] ^ S[tk[(i+1)%4][Nk-1]];
					//printf("%i %i\n", t, tk[i][0]);
			    }
		
			    tk[0][0] = tk[0][0] ^ rcon[rconpocharer++];
                // printf("%i %i\n", t, tk[0][0]);
			    for(int j = 1; j < Nk; j++)

				    for (int i = 0; i < 4; i++) {

					     tk[i][j] = tk[i][j] ^ tk[i][j-1];
                         //printf("%i %i\n", t, tk[i][j]);

			        }

			    for(int j = 0; (j < Nk) && (t<(Nr+1)*Nb); j++, t++)

			        for(int i = 0; i < 4; i++) {

				      key[t / Nb][i][t % Nb] = tk[i][j];
                      //printf("%i %i %i %i %i\n", t/Nb, i, t%Nb, key[t / Nb][i][t % Nb], tk[i][j]);

			        } 
		        //t = t+4;

		 } 

	}

string AES128::blockEncrypt(string in){

		for (int i=0; i<4; i++) {
			for (int j=0; j<Nb; j++) {
				state[j][i]= (int)(in[i*4+j]);
			}
		} 

		showInt("Cifrado: <br/> State Inicial");
		KeyAddition(key[0]); 
		showInt("Ronda 0 :: KeyAddition");
	    
		for(int r = 1; r < Nr; r++) {
			 ByteSubShiftRow();
             char outStr[128];
             sprintf_s(outStr,128,"%s %i %s", "Ronda", r, ":: ByteSubShiftRow");
			 showInt(outStr);

			 MixColumnKeyAddition(key[r]); 
             
             sprintf_s(outStr,128,"%s %i %s", "Ronda", r, ":: MixColumnKeyAddition");
			 showInt(outStr);
			
		}

		ByteSubShiftRow();
		showInt("Ronda final :: ByteSubShiftRow");

		KeyAddition(key[Nr]); 
		showInt("Ronda final :: KeyAddition ==> Resultado");

		string out("");

		for(int i=0; i<4; i++)
		   for (int j=0; j<4; j++)
			   out+=static_cast<char>(state[j][i]); 

			if(!!(toHex)) {
			  out=toHexString(out);
			}  

		return out;

	}  
string AES128::blockDecrypt(string in) {

        //printf("in=%s \n", in.c_str());
		for (int i=0; i<4; i++){
			for (int j=0; j<Nb; j++){
         		 state[j][i]=(int)(unsigned char)(in[i*4+j]);
				 //printf ("f %i %i %i \n", j,i,state[j][i]);

			}

		} 

		showInt("Descifrado: <br/> State Inicial");        

		KeyAddition(key[Nr]); 

		showInt("Ronda 0 :: KeyAddition");

		for(int r = Nr-1; r > 0; r--) {

			InvShiftRowInvByteSub();

			char outStr[128];
            sprintf_s(outStr, 128,"%s %i %s", "Ronda",(Nr - r), " :: InvShiftRowInvByteSub");
			showInt(outStr);

			KeyAddition(key[r]); 

            sprintf_s(outStr, 128,"Ronda", (Nr - r), " :: KeyAddition");
			showInt(outStr);

			InvMixColumn();

            sprintf_s(outStr, 128,"Ronda", (Nr - r), ":: InvMiColumn");
			showInt(outStr);

		 }

		 InvShiftRowInvByteSub();  

		 showInt("Ronda final :: InvShiftRowInvByteSub"); 

		 KeyAddition(key[0]); 

		 showInt("Ronda final :: KeyAddition ==> Resultado");

		 string out("");

		 for(int i=0; i<4; i++)
			 for (int j=0; j<4; j++) {
				out = out + static_cast<char>(state[j][i]);  
                //printf ("b %i %i %i \n", j,i,state[j][i]);
			 }
		    if(!!(toHex)){

				out = toHexString(out);

			}            

		return out;

	}      
string AES128::toHexString (string sa) {

	   string buf("");

	   for (int i=0; i<sa.size(); i++){

           char outStr[256];
           sprintf_s(outStr,32, "%x",(int)sa[i]);	
		   string val(outStr);

		   if (val.size()< 2) 
			  val= "0" + val;
		   buf = buf + val;

	   }

		return buf;

	}      
    
string AES128::fromHexString(string sa) {

		  string buf("");
		 

		  for(int i=0; i<sa.size(); i=i+2) {

            char outStr[256];
            sprintf_s(outStr,32,"%s%s",sa[i], sa[i+1]);	

            int hexInt; 
            sscanf_s(outStr, "%x", &hexInt, 32); 

			sprintf_s(outStr,32,"%c", hexInt);

            string val(outStr);

			buf = buf + val;

		  }
		  
		 return buf;

	}


void AES128::showInt(char *texto) {

	if (debug) {

		printf("%s:", texto);

		for(int i=0; i<4; i++) {

		    for(int j=0; j<4; j++){

		      char outStr[256];
              sprintf_s(outStr,32,"%x",state[i][j]);

			  printf("%s ", outStr);
			}

		}
	    printf("<br/>");

	} 
} 

string AES128::padTo16API(string s) {

	int l = (int)s.size();
    string r(s);

	int n = 16 - (l % 16);	
	
	for (int i=0; i < n; i++) {
		r = r+" ";
	}
	return r;
}

string AES128::aesAPIEncrypt(string pt) {
	
	pt = padTo16API(pt);
	int ptlen = (int)pt.size();
//	echo 'pt: ['.$pt.'], len: '.$ptlen.'<p>';
	string ct;
	int offset = 0;

	do {
//	  	echo 'encrypt: ['.substr($pt,$offset,16)."]\n";
  		ct = ct + blockEncrypt(pt.substr(offset,16));
  		offset = offset + 16;
	  	//if ($offset < $ptlen) { echo 'Next> offset: '.$offset.'<p>'; }
	} while (ptlen > offset);
    //printf("ct=%s \n", ct.c_str());
    //unsigned char *a = (unsigned char *)ct.c_str();
	//for (int i=0; i<ct.size(); i++) {
    //    printf("%i=%i \n", i, (int)a[i]);
    //}
//	return $ct;
	return base64_encode((const unsigned char*)(ct.c_str()), ct.size());
}

string AES128::aesAPIDecrypt(string ct) {
		
	string ct1;
	ct1 = base64_decode(ct);
	int ctlen = ct1.size();
	string pt;
	int offset = 0;
	do {
  		pt = pt + blockDecrypt(ct1.substr(offset,16));
  		offset = offset + 16;

	} while (ctlen > offset);
	return pt;
}